﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.IO;
using System.Web.Services.Description;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Xml.Serialization;

namespace Encosia
{
  [ToolboxData("<{0}:UsernameAvailabilityValidator ErrorMessage=\"Username not available\" runat=\"server\"></{0}:UsernameAvailabilityValidator>")]
  public class UsernameAvailabilityValidator : BaseValidator, IScriptControl
  {
    protected ScriptManager _sm;

    #region Properties
    /// <summary>
    /// The path to the web service.
    /// </summary>        
    [DefaultValue("")]
    [Description("The path to the web service.")]
    public string ServicePath
    {
      get { return GetValue("ServicePath", String.Empty); }
      set
      {
        string url = ResolveUrl(value);
        SetValue("ServicePath", url);
      }
    }

    /// <summary>
    /// The web service method to be called in order to validate the username.
    /// </summary>        
    [DefaultValue("")]
    [Description("The web service method to be called in order to validate the username.")]
    public string ServiceMethod
    {
      get { return GetValue("ServiceMethod", String.Empty); }
      set { SetValue("ServiceMethod", value); }
    }

    /// <summary>
    /// Gets and sets a value indicating validation happens on keypress.
    /// </summary>        
    [DefaultValue(false)]
    [Description("Gets and sets a value indicating validation happens on keypress.")]
    public bool ValidateOnKeyPress
    {
      get { return GetValue("ValidateOnKeyPress", false); }
      set { SetValue("ValidateOnKeyPress", value); }
    }
    /// <summary>
    /// Gets and sets a value indicating minimum number of characters before validation begins.
    /// </summary>        
    [DefaultValue(4)]
    [Description("Gets and sets a value indicating minimum number of characters before validation begins.")]
    public byte MinimumLength
    {
      get { return GetValue("MinimumLength", (byte)4); }
      set
      {
        if (value > 0)
        {
          SetValue("MinimumLength", value);
        }
        else
        {
          throw new Exception("MinimumLength must be greater than 0.");
        }
      }
    }
    /// <summary>
    /// Gets and sets a value indicating how long after keyup validation happens.
    /// </summary>        
    [DefaultValue(500)]
    [Description("Gets and sets a value indicating how long after keyup validation happens.")]
    public int KeyPressDelay
    {
      get { return GetValue("KeyPressDelay", 500); }
      set
      {
        if (value > 0)
        {
          SetValue("KeyPressDelay", value);
        }
        else
        {
          throw new Exception("KeyPressDelay must be greater than 0.");
        }
      }
    }
    #endregion

    protected override bool EvaluateIsValid()
    {
      string controlValidationValue = base.GetControlValidationValue(base.ControlToValidate);
      return IsUsernameAvailable(controlValidationValue);
    }

    private bool IsUsernameAvailable(string controlValidationValue)
    {
      bool retVal = false;
      if (!string.IsNullOrEmpty(controlValidationValue) && controlValidationValue.Length >= MinimumLength)
      {
        WebClient client = new WebClient();
        HttpContext context = HttpContext.Current;
        Uri absoluteBaseUri = new Uri(context.Request.Url.AbsoluteUri);
        Uri uri = null;
        Uri.TryCreate(absoluteBaseUri, ServicePath + "?wsdl", out uri);
        Stream stream = client.OpenRead(uri);
        ServiceDescription description = ServiceDescription.Read(stream);
        ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
        importer.ProtocolName = "Soap12";
        importer.AddServiceDescription(description, null, null);
        importer.Style = ServiceDescriptionImportStyle.Client;
        importer.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties;
        CodeNamespace nmspace = new CodeNamespace();
        CodeCompileUnit ccu = new CodeCompileUnit();
        ccu.Namespaces.Add(nmspace);
        ServiceDescriptionImportWarnings warning = importer.Import(nmspace, ccu);
        if (warning == 0)
        {
          CodeDomProvider cdp = CodeDomProvider.CreateProvider("CSharp");
          string[] assemblyReferences = new string[5] { "System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll" };
          CompilerParameters parameters = new CompilerParameters(assemblyReferences);
          CompilerResults result = cdp.CompileAssemblyFromDom(parameters, ccu);
          Type[] types = result.CompiledAssembly.GetTypes();
          object webservice = result.CompiledAssembly.CreateInstance(types[0].Name);
          MethodInfo mi = webservice.GetType().GetMethod(ServiceMethod);
          retVal = (bool)(mi.Invoke(webservice, new object[] { controlValidationValue }));
        }
      }
      return retVal;
    }

    public virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
      ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Encosia.UsernameAvailabilityValidator", this.ClientID);

      descriptor.AddProperty("ValidateOnKeyPress", ValidateOnKeyPress);
      descriptor.AddProperty("MinimumLength", MinimumLength);
      descriptor.AddProperty("KeyPressDelay", KeyPressDelay);
      descriptor.AddProperty("ServicePath", ServicePath);
      descriptor.AddProperty("ServiceMethod", ServiceMethod);

      yield return descriptor;
    }

    public virtual IEnumerable<ScriptReference> GetScriptReferences()
    {
      ScriptReference reference = new ScriptReference();

      reference.Assembly = "UsernameAvailabilityValidator";

      if (HttpContext.Current.IsDebuggingEnabled)
        reference.Name = "Encosia.UsernameAvailabilityValidator.js";
      else
        reference.Name = "Encosia.UsernameAvailabilityValidator.min.js";

      yield return reference;
    }

    #region Control life-cycle events
    protected override void OnPreRender(EventArgs e)
    {
      if (!this.DesignMode && this.DetermineRenderUplevel())
      {
        // Test for ScriptManager and register if it exists
        _sm = ScriptManager.GetCurrent(Page);

        if (_sm == null)
          throw new HttpException("A ScriptManager control must exist on the current page.");

        _sm.RegisterScriptControl(this);
      }
      base.OnPreRender(e);
    }

    protected override void Render(HtmlTextWriter writer)
    {
      if (!this.DesignMode && this.DetermineRenderUplevel())
        _sm.RegisterScriptDescriptors(this);

      base.Render(writer);
    }
    #endregion

    #region ViewState Helpers
    private T GetValue<T>(string key, T nullValue)
    {
      if (ViewState[key] == null)
        return nullValue;
      else
        return (T)ViewState[key];
    }
    private void SetValue<T>(string key, T value)
    {
      ViewState[key] = value;
    }
    #endregion
  }
}
